/**
 * 
 */
package ro.dta.idbi.ui.patterns;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JLabel;

import ro.dta.idbi.interfaces.sql.ISqlColumn;
import ro.dta.idbi.interfaces.sql.ISqlConstruct;
import ro.dta.idbi.interfaces.sql.ISqlForeignKey;
import ro.dta.idbi.interfaces.sql.ISqlTable;
import ro.dta.idbi.interfaces.ui.IMainFrame;
import ro.dta.idbi.patterns.RedundantFK;
import ro.dta.idbi.ui.sql.SqlForeignKeyCell;
import ro.dta.idbi.ui.sql.SqlGraph;
import ro.dta.idbi.ui.sql.SqlTableCell;
import uk.ac.ic.doc.automed.reps.Schema;

import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.mxGraphComponent;

/**
 * UI for the <i>redudant foreign key removal pattern</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class RedundantFKDialog extends AbstractPatternDialog {

	private static final long serialVersionUID = -4937820248851775405L;

	private final static Logger LOGGER = Logger
			.getLogger("com.dta.idbi.patterns.ui.RedundantFKDialog");

	/**
	 * Check if the current pattern can be applied over a given set of objects
	 * 
	 * Used to check if the items selected by the user in the UI can be used to
	 * apply this transformation pattern over them
	 * 
	 * @param schema
	 *            Schema in the context of which to check the validity of the
	 *            array of objects
	 * @param objects
	 *            Set of objects
	 * @return True if pattern can be applied over set of objects, false
	 *         otherwise
	 */
	public static boolean isValid(final Schema schema, final Object[] objects) {
		if (objects.length != 1) {
			return false;
		}

		if (!(objects[0] instanceof ISqlForeignKey)) {
			return false;
		}

		ISqlForeignKey fk = (ISqlForeignKey) objects[0];
		ISqlTable sourceTbl = (ISqlTable) fk.getSource();
		ISqlTable targetTbl = (ISqlTable) fk.getTarget();
		List<ISqlTable> sourceSuper = sourceTbl.getSupertypes();
		List<ISqlTable> targetSuper = targetTbl.getSupertypes();

		for (ISqlTable t : sourceSuper) {
			List<ISqlForeignKey> fks = t.getForeignKeys();
			for (ISqlForeignKey f : fks) {
				// One of the target's supertypes is the target of this FK (case
				// a)
				// or targetTbl is the target of this FK (case b)
				if (f.getTarget() == targetTbl || targetSuper.contains(f.getTarget())) {
					return true;
				}
			}
		}

		return false;
	}

	private ISqlTable sourceParent;
	private ISqlTable targetParent;

	/**
	 * Reverse normalisation pattern dialog class constructor
	 * 
	 * @param parentFrame
	 *            Parent frame of this dialog
	 * @param schema
	 *            Schema over which the transformation is applied
	 * @param element
	 *            Sql construct(s) over which the transformation is applied
	 * @param beforeLabel
	 *            Label for the before diagram
	 * @param afterLabel
	 *            Label for the transformed diagram
	 */
	public RedundantFKDialog(final IMainFrame parentFrame, final Schema schema,
			final ISqlConstruct[] element, final JLabel beforeLabel, final JLabel afterLabel) {
		super(parentFrame, schema, element, beforeLabel, afterLabel);

		if (!isValid(schema, element)) {
			throw new IllegalArgumentException(
					"This pattern can't be applied to the given elements");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ro.dta.idbi.ui.patterns.AbstractPatternDialog#createOriginalDiagram()
	 */
	@Override
	protected mxGraphComponent createOriginalDiagram() {
		initialise();
		final SqlGraph graph = new SqlGraph();
		ISqlTable[] tables = addTables(graph);
		graph.getModel().beginUpdate();
		try {
			ISqlForeignKey fk1 = new SqlForeignKeyCell(null, null, "", "", tables[0], tables[1],
					"FK_EDGE");
			graph.addCell(fk1, graph.getDefaultParent());
		} finally {
			graph.getModel().endUpdate();
		}
		return new mxGraphComponent(graph);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ro.dta.idbi.ui.patterns.AbstractPatternDialog#createTransformedDiagram()
	 */
	@Override
	protected mxGraphComponent createTransformedDiagram() {
		final SqlGraph graph = new SqlGraph();
		addTables(graph);
		return new mxGraphComponent(graph);
	}

	private ISqlTable[] addTables(SqlGraph graph) {
		ISqlForeignKey fk = (ISqlForeignKey) element[0];
		ISqlTable sourceTbl = (ISqlTable) fk.getSource();
		ISqlTable targetTbl = (ISqlTable) fk.getTarget();
		ISqlTable[] result = new ISqlTable[4];
		graph.getModel().beginUpdate();
		try {
			final SqlTableCell newTable1 = (SqlTableCell) sourceTbl.clone();
			mxGeometry newGeometry = new mxGeometry(5, 5, sourceTbl.getGeometry().getWidth(),
					sourceTbl.getGeometry().getHeight());
			newTable1.setGeometry(newGeometry);
			newTable1.relayout(graph);
			result[0] = newTable1;

			final SqlTableCell newTable2 = (SqlTableCell) targetTbl.clone();
			newGeometry = new mxGeometry(200, 5, targetTbl.getGeometry().getWidth(), targetTbl
					.getGeometry().getHeight());
			newTable2.setGeometry(newGeometry);
			newTable2.relayout(graph);
			result[1] = newTable2;

			final SqlTableCell newTable3 = (SqlTableCell) sourceParent.clone();
			newGeometry = new mxGeometry(200, 5, sourceParent.getGeometry().getWidth(),
					sourceParent.getGeometry().getHeight());
			newTable3.setGeometry(newGeometry);
			newTable3.relayout(graph);
			result[2] = newTable3;

			ISqlForeignKey fk1 = new SqlForeignKeyCell(null, null, "", "child of", newTable1,
					newTable3, "FK_EDGE");
			graph.addCell(fk1, graph.getDefaultParent());

			if (targetParent != null) {
				final SqlTableCell newTable4 = (SqlTableCell) targetParent.clone();
				newGeometry = new mxGeometry(200, 5, targetParent.getGeometry().getWidth(),
						targetParent.getGeometry().getHeight());
				newTable4.setGeometry(newGeometry);
				newTable4.relayout(graph);
				result[3] = newTable4;

				fk1 = new SqlForeignKeyCell(null, null, "", "", newTable3, newTable4, "FK_EDGE");

				ISqlForeignKey fk2 = new SqlForeignKeyCell(null, null, "", "child of", newTable2,
						newTable4, "FK_EDGE");
				graph.addCell(fk2, graph.getDefaultParent());
			} else {
				fk1 = new SqlForeignKeyCell(null, null, "", "", newTable3, newTable2, "FK_EDGE");
			}
			graph.addCell(fk1, graph.getDefaultParent());
		} catch (CloneNotSupportedException e) {
			LOGGER.log(Level.SEVERE, "Fatal exception", e);
		} finally {
			mxHierarchicalLayout layout = new mxHierarchicalLayout(graph);
			layout.execute(graph.getDefaultParent());
			graph.setAutoSizeCells(true);
			graph.getModel().endUpdate();
		}

		return result;
	}

	/**
	 * Initialise the pattern
	 */
	private void initialise() {
		ISqlForeignKey fk = (ISqlForeignKey) element[0], fk2 = null;
		ISqlTable sourceTbl = (ISqlTable) fk.getSource();
		ISqlTable targetTbl = (ISqlTable) fk.getTarget();
		List<ISqlTable> sourceSuper = sourceTbl.getSupertypes();
		List<ISqlTable> targetSuper = targetTbl.getSupertypes();

		for (ISqlTable t : sourceSuper) {
			List<ISqlForeignKey> fks = t.getForeignKeys();
			for (ISqlForeignKey f : fks) {
				if (f.getTarget() == targetTbl) {
					// targetTbl is the target of this FK (case b)
					sourceParent = t;
					fk2 = f;
				} else if (targetSuper.contains(f.getTarget())) {
					// One of the target's supertypes is the target of this FK
					// (case a)
					sourceParent = t;
					targetParent = (ISqlTable) f.getTarget();
					fk2 = f;
				}
			}
		}		

		StringBuilder macro = new StringBuilder(RedundantFK.COMMAND);
		macro.append("(");
		macro.append(fkToString(fk)).append(", ");
		macro.append(fkToString(fk2));
		macro.append(")");
		macroField.setText(macro.toString());
	}
	
	private String fkToString(ISqlForeignKey fk) {
		StringBuilder macro = new StringBuilder();
		macro.append("<<").append(fk.getValue()).append(",");
		macro.append(fk.getSource().getValue()).append(",");
		for (ISqlColumn c : fk.getSourceCols()) {
			macro.append(c.getShortDefinition()).append(",");
		}
		macro.append(fk.getTarget()).append(",");
		for (ISqlColumn c : fk.getTargetCols()) {
			macro.append(c.getShortDefinition()).append(",");
		}
		macro.setLength(macro.length() - 1);
		macro.append(">>");
		return macro.toString();
	}

}
